import React from "react";
import styles from './index.module.scss';
import {
  ArrowsAltOutlined,
  CodepenOutlined,
  ShrinkOutlined,
  QuestionCircleOutlined,
  FormatPainterOutlined,
  CloseCircleOutlined,
  SmileOutlined,
  ClockCircleTwoTone
} from '@ant-design/icons';
import {DraggableModalBox} from '@/components';
import {chartFrameType} from '.././constants/EnumDv';

// ACE 依赖
import AceEditor from "react-ace";
import "ace-builds/webpack-resolver";// 必须引入该依赖，解决webWorker问题
import 'ace-builds/src-noconflict/ext-language_tools'; // 语法自动提示
// import 'ace-builds/src-noconflict/worker-javascript'; // 代码检查工具
import {beautify} from 'ace-builds/src-noconflict/ext-beautify'; // 代码格式化
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/snippets/javascript"; // js语法片段
import 'ace-builds/src-noconflict/theme-tomorrow'; // 主题
import 'ace-builds/src-noconflict/ext-searchbox'; // 语法自动提示
// TS
import {Ace} from "ace-builds";
import Completion = Ace.Completion;
import Editor = Ace.Editor;

import {Button, Tooltip} from "@/components/wocloud-antd";
import {SaveOutlined} from "@ant-design/icons/lib";
import dvTool from "@/pages/dv/dvTool";

import completions_common from "./language_tools/common_keywords";
import * as classnames from "classnames";


interface IProps {
  uid: string;
  frameType?: string;
  codeConfig: DV.ICodeConfig;
}

type IMode = 'modal' | 'inside';
interface IState {
  size: {
    width: number;
    height: number;
  },
  code: string;
  saveStatus: boolean;
  changeStatus: boolean;
  errorMsg: string[];
  mode: IMode;
}

export default class CodeEditorBox extends React.PureComponent<IProps, IState> {
  public editor: Editor;
  public errorAnnotations: any[]; // 语法错误集合

  constructor(props: IProps) {
    super(props);
    this.state = {
      size: {
        width: 500,
        height: 400
      },
      code: props.codeConfig.code || '',
      errorMsg: [],
      saveStatus: false,
      changeStatus: false,
      mode: 'inside'
    }
    this.errorAnnotations = [];
  }

  componentDidMount(): void {

  }


  onChange = (newValue: string) => {
    dvTool.isEditingOfChartCode = true;
    this.setState({
      code: newValue,
      saveStatus: false,
      changeStatus: true
    })
  }

  /**
   * 保存代码
   * @param code
   */
  saveCode = (code: string) => {
    this.applyCode(code, () => {
      // 没问题就保存
      dvTool.updateCodeConfig(this.props.uid, {
        code
      })
      dvTool.isEditingOfChartCode = false;
      this.setState({
        saveStatus: true,
        changeStatus: false
      })
    });
  }

  /**
   * 应用代码
   * @param code
   * @param onOk
   */
  applyCode = (code: string, onOk?: () => void) => {
    // MD 1.检查代码
    this.checkCode(code).then(resp => {
      // MD 2.执行代码
      this.runCode(code, () => {
        this.setState({
          errorMsg: [],
        });
        this.errorAnnotations = [];
        onOk && onOk();
      });
    }, resp => {
      const {row, column} = resp[0];
      this.setState({
        errorMsg: ['编辑器内容语法有误！', `row: ${row}, column: ${column}`]
      });
      this.errorAnnotations = resp;
    });

  }

  /**
   * 检查代码语法
   * @param code
   */
  checkCode(code: string) {

    return new Promise(((resolve, reject) => {
      const annotations = this.editor.getSession().getAnnotations();
      const errorAnnotations = annotations.filter(item => item.type === 'error');
      console.log(code, errorAnnotations);
      if (errorAnnotations.length > 0) {
        reject(errorAnnotations);
      } else {
        resolve();
      }
    }))
  }

  getData() {
    return [{name: 1}];
  }

  /**
   * 执行代码
   * @param code
   * @param onOk
   */
  runCode(code: string, onOk: () => void) {
    let newOption = dvTool.getChartConfig(this.props.uid, {codeConfig: {code}});
    dvTool.reloadChart(newOption, () => {
      onOk && onOk();
    }, (e) => {
      this.setState({
        errorMsg: ['[编程运行错误]: ' + e.message, '']
      })
    });
  }

  /**
   * 格式化代码
   */
  formatCode = () => {
    beautify(this.editor.getSession());
  }

  /**
   * 定位到错误行数
   */
  findErrorRow = () => {
    const {row, column} = this.errorAnnotations[0];
    this.editor.scrollToRow(row);
    this.editor.gotoLine(row + 1, column, true);
  }

  /**
   * 编辑器加载完成时
   * @param editor
   */
  async complete(editor: any) {
    this.editor = editor;
    // MD 1、动态加载组件框架对应的语法提示
    let frameCompletion: Completion[] = [];
    switch (this.props.frameType) {
      case chartFrameType.echarts.type:
        const Echarts_completion = await import(/*webpackChunkName: `echarts_keyword`*/'./language_tools/echarts_keywords').then(({default: completions}) => completions);
        frameCompletion = frameCompletion.concat(Echarts_completion);
        break;
      case chartFrameType.hcharts.type:
        break;
      default:
        break;
    }

    const completions: Completion[] = (completions_common || []).concat(frameCompletion);

    // MD 2、为编辑器注入自动提示语句
    editor.completers.push({
      getCompletions: function (editor, session, pos, prefix, callback) {
        callback(null, completions);
      }
    });
  }

  onChangeMode(mode: IMode){
    this.setState({
      mode
    })
  }

  /**
   * 渲染内容组件
   */
  getContent(){
    return <React.Fragment>
      <AceInfoTip
        errorMsg={this.state.errorMsg}
        saveStatus={this.state.saveStatus}
        findErrorRow={this.findErrorRow}
      />
      <ul className={styles.tool}>
        {/*左侧互动按钮*/}
        <li>
          {
            this.state.mode === 'inside' && <Tooltip title={'悬浮'}><ArrowsAltOutlined onClick={() => this.onChangeMode('modal')}/></Tooltip>
          }
          {
            this.state.mode === 'modal' && <Tooltip title={'内置'}><ShrinkOutlined onClick={() => this.onChangeMode('inside')}/></Tooltip>
          }
        </li>
        <li><Tooltip title={'格式化代码'}><FormatPainterOutlined onClick={this.formatCode}/></Tooltip></li>
        {this.state.changeStatus && <li><Tooltip title={'代码已变更，请注意保存'}><ClockCircleTwoTone/></Tooltip></li>}
        <li><Tooltip title={'帮助文档'}><QuestionCircleOutlined/></Tooltip></li>
        {/*右侧互动按钮*/}
        <li className={styles.apply}>
          <Tooltip title={'只运行不保存'}>
            <Button size="small" type={'primary'} onClick={() => this.applyCode(this.state.code)}>运行</Button>
          </Tooltip>
        </li>
        <li className={styles.save}>
          <Tooltip title={'保存并运行'}>
            <Button
              size="small"
              type={'primary'}
              icon={<SaveOutlined/>}
              onClick={() => this.saveCode(this.state.code)}
            />
          </Tooltip>
        </li>
      </ul>
      <AceEditor
        {...this.getAceProps()}
        height={'calc(100% - 50px)'}
      />
    </React.Fragment>
  }

  // ace组件属性配置
  getAceProps(){
    return {
      placeholder: '/**\n' +
        ' * @param chart       组件属性配置\n' +
        ' * @param myChart  图表实例\n' +
        ' * @param box          容器\n' +
        ' * @param data         数据\n' +
        ' * @param dv            大屏函数工具\n' +
        ' * @param _              lodash\n' +
        ' */',
      width: '100%',
      height: 'calc(100% - 24px)',
      mode: "javascript",
      theme: "tomorrow",
      onChange: this.onChange,
      name: "UNIQUE_ID_OF_DIV",
      editorProps: {
        $blockScrolling: true
      },
      fontSize: 12,
      value: this.state.code,
      showPrintMargin: true,
      showGutter: true,
      highlightActiveLine: true,
      setOptions: {
        enableBasicAutocompletion: true,
        enableLiveAutocompletion: true,
        enableSnippets: true,
        showLineNumbers: true,
        tabSize: 2,
      },
      onLoad: this.complete.bind(this),
      commands: [{    //命令是键绑定数组。
        name: 'saveFile', //键绑定的名称。
        bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, //用于命令的组合键。
        exec: () => {
          this.saveCode(this.state.code);
        }   //重新绑定命令的名称
      }]
    }
  }

  getContainer(){
    switch (this.state.mode) {
      case "inside":
        return <React.Fragment>
          {this.getContent()}
        </React.Fragment>
      case "modal":
        return <DraggableModalBox
          title={'组件函数编程'}
          onClose={() => this.onChangeMode('inside')}
        >
          {this.getContent()}
        </DraggableModalBox>
      default:
        return <React.Fragment>
          {this.getContent()}
        </React.Fragment>
    }
  }

  render(): React.ReactNode {

    return <div className={styles.codeEditorBox}>
      {this.getContainer()}
      {
        this.state.mode !== 'inside' && <div>
          <div className={styles.codingSign}>
            <CodepenOutlined/>
            <div>请在悬浮模态框中编程</div>
          </div>
        </div>
      }
    </div>
  }
}

/**
 * 错误提示框
 * @param errorMsg
 * @param findErrorRow
 * @constructor
 */
interface IAceInfoTip {
  errorMsg: string[];
  saveStatus: boolean;

  findErrorRow(): void;
}

function AceInfoTip({errorMsg = [], saveStatus = false, findErrorRow}: IAceInfoTip) {
  return <div className={classnames(styles.errorTip, {
    [styles.hidden]: !errorMsg[0] && !saveStatus,
    [styles.success]: saveStatus
  })}>
    {
      !!errorMsg[0] && <React.Fragment>
        <span className={styles.errorMsg}><CloseCircleOutlined/> {errorMsg[0]}</span>
        <Tooltip title={'定位到语法错误位置'}>
          <span className={styles.row} onClick={findErrorRow}>{errorMsg[1]}</span>
        </Tooltip>
      </React.Fragment>
    }
    {
      saveStatus && <span className={styles.successMsg}><SmileOutlined/> 已保存 </span>
    }

  </div>
}
